<?php
namespace App\Service;
use Psr\Log\LoggerInterface;
use Hyperf\Logger\LoggerFactory;


class MachineService{

    protected LoggerInterface $logger;

    public function __construct(LoggerFactory $loggerFactory)
    {
        // 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key
        $this->logger = $loggerFactory->get('log', 'default');
    }

    // 帧头
    const FRAME_HEADER = "5AA5";
    // 帧尾
    const FRAME_TAIL = "7887";

    // 指令码
    const CMD_SIGN_IN = "A1"; // 签到指令
    const CMD_QUERY_BALANCE = "02"; // 查询余额
    const CMD_OPEN_SOCKET = "03"; // 刷卡打开插座
    const CMD_CLOSE_SOCKET = "04"; // 充电关闭上报
    const CMD_HEARTBEAT = "08"; // 心跳
    const CMD_START_CHARGE = "20"; // 开始充电
    const CMD_STOP_CHARGE = "20"; // 停止充电
    const CMD_REPORT = "24"; // 充电过程上报
    const CMD_REBOOT = "40"; // 重启
    const CMD_UPGRADE = "F0"; // 升级指令
    const CMD_SET = "54"; // 下发从机编号

    /**
     * 发送签到指令
     * @param string $stationId 充电站编号
     * @param array $data 数据包
     * @return array
     */
    public function sendSignIn($stationId,$data)
    {
        $command = $this->buildCommand($stationId, self::CMD_SIGN_IN, $data);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 查询余额
     * @param string $stationId 充电站编号
     * @param string $cardNumber 卡号
     * @return array
     */
    public function queryBalance($stationId,$cardNumber)
    {
        $data = [
            'card_number' => $cardNumber,
        ];
        $command = $this->buildCommand($stationId, self::CMD_QUERY_BALANCE, $data);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 打开插座
     * @param string $stationId 充电站编号
     * @param int $socketId 插座编号
     * @param string $cardNumber 卡号
     * @return array
     */
    public function openSocket($stationId,$socketId,$cardNumber)
    {
        $data = [
            'socket_id' => $socketId,
            'card_number' => $cardNumber,
        ];
        $command = $this->buildCommand($stationId, self::CMD_OPEN_SOCKET, $data);
        return $this->sendCommand($stationId, $command);
    }

    public function ipset($stationId)
    {
        $data = [
            'ver' => '00000000250227A0'
        ];
        $command = $this->buildCommand($stationId, self::CMD_UPGRADE, $data,'00','01');
        return $this->sendCommand($stationId, $command);
    }



    /**
     * 关闭插座
     * @param string $stationId 充电站编号
     * @param int $socketId 插座编号
     * @return array
     */
    public function closeSocket($stationId,$socketId)
    {
        $data = [
            'socket_id' => $socketId,
        ];
        $command = $this->buildCommand($stationId, self::CMD_CLOSE_SOCKET, $data);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 发送心跳
     * @param string $stationId 充电站编号
     * @return array
     */
    public function sendHeartbeat($stationId)
    {
        $command = $this->buildCommand($stationId, self::CMD_HEARTBEAT, []);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 开始充电
     * @param string $stationId 充电站编号
     * @param int $socketId 插座编号
     * @param int $duration 充电时长（分钟）
     * @return array
     */
    public function startCharge($stationId,$socketId,$duration = 0)
    {
        $data = [
            'socket_id' => $socketId,
            'operate' => 1,
            'duration' =>dechex((int)$duration),
        ];
        $command = $this->buildCommand($stationId, self::CMD_START_CHARGE, $data);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 停止充电
     * @param string $stationId 充电站编号
     * @param int $socketId 插座编号
     * @return array
     */
    public function stopCharge($stationId,$socketId)
    {
        $data = [
            'socket_id' => $socketId,
            'operate' => 0,
            'duration' => 0,
        ];
        $command = $this->buildCommand($stationId, self::CMD_STOP_CHARGE, $data);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 停止过程上报
     * @param string $stationId 充电站编号
     * @param int $socketId 插座编号
     * @return array
     */
    public function reportCharge($stationId,$socketId)
    {
        $data = [
            'socket_id' => $socketId,
        ];
        $command = $this->buildCommand($stationId, self::CMD_REPORT, $data);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 重启充电站
     * @param string $stationId 充电站编号
     * @return array
     */
    public function rebootStation($stationId)
    {
        $command = $this->buildCommand($stationId, self::CMD_REBOOT, []);
        return $this->sendCommand($stationId, $command);
    }


    /**
     * 设置充电站
     * @param string $stationId 充电站编号
     * @return array
     */
    public function setStation($stationId,$socket)
    {
        //5AA511005429 54 01 11 01 02071393 01 02071393 02 00007887
        redis()->set($stationId.":SET",json_encode(array("ctime"=>time(),"data"=>$socket)));

        $sockets = json_decode($socket,true);
        $sn = array_unique(array_column($sockets,'sn'));
        $pack = str_pad((string)count($sn), 2, '0', STR_PAD_LEFT);

        $command = $this->buildCommand($stationId, self::CMD_SET, ['ver'=>$pack]);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 升级充电站固件
     * @param string $stationId 充电站编号
     * @param string $firmwareVersion 固件版本号
     * @return array
     */
    public function upgradeFirmware($stationId, $firmwareVersion)
    {
        $data = [
            'firmware_version' => $firmwareVersion,
        ];
        $command = $this->buildCommand($stationId, self::CMD_UPGRADE, $data);
        return $this->sendCommand($stationId, $command);
    }

    /**
     * 构建命令
     * @param string $stationId 充电站编号
     * @param string $cmd 指令码
     * @param array $data 数据包
     * @return string
     */
    private function buildCommand($stationId, $cmd, $data,$zhen = '00',$err = '00')
    {
        $frameHeader = self::FRAME_HEADER;
        $frameTail = self::FRAME_TAIL;

        // 构建数据包
        $dataPacket = $this->buildDataPacket($data);

        // 计算数据长度
        $dataLength = (strlen($dataPacket) / 2) + (strlen($err) / 2); // 数据长度为字节数
        $len = str_pad(dechex($dataLength), 2, '0', STR_PAD_LEFT);


        // 构建命令
        $command = $frameHeader . $stationId . $cmd .$zhen.$len .$err. $dataPacket;

        // 计算校验码
        $checksum = $this->calculateChecksum($command);

        // 完整命令
        $fullCommand = $command . $checksum . $frameTail;

        $log = "HTTP请求---$stationId|$cmd|".$fullCommand;
        echo $log."\n";
        $this->logger->info($log);

        return strtoupper($fullCommand);
    }

    /**
     * 构建数据包
     * @param array $data 数据
     * @return string
     */
    private function buildDataPacket($data)
    {
        $packet = '';
        foreach ($data as $key => $value) {
            $packet .= str_pad((string)$value, 2, '0', STR_PAD_LEFT);
        }
        return $packet;
    }

    /**
     * 计算校验码
     * @param string $command 命令
     * @return string
     */
    private function calculateChecksum($command)
    {
//        // 这里使用 CRC16 校验算法
//        $crc = crc16($command);
//        return str_pad((string)dechex($crc), 4, '0', STR_PAD_LEFT);
        return '0000';
    }

    /**
     * 发送命令
     * @param string $stationId 充电站编号
     * @param string $command 命令
     * @return array
     */
    private function sendCommand($stationId, $command)
    {
        $macnoData = self::getRedis($stationId  . ":COM");
        if(empty($macnoData)){
            return $this->parseResponse(['result'=>1]);
        }
        $macnoData = json_decode((string)$macnoData,true);
        $fd = !empty($macnoData['port'])?$macnoData['port']:0;
        if(empty($fd)){
            return $this->parseResponse(['result'=>2]);
        }

        server()->send($fd,hex2bin($command));

        return $this->parseResponse(['result'=>0]);
    }

    /**
     * 解析响应
     * @param string $response 响应数据
     * @return array
     */
    private function parseResponse($response,$code = 1,$msg = '成功')
    {
        // 解析响应数据
        $data = [
            'code' => $code,
            'msg' => $msg,
            'data' => $response,
        ];
        return $data;
    }


    /**
     * 设置Redis数据
     * @param string $key Redis键
     * @param string $value Redis值
     */
    private static function setRedis($key, $value)
    {
        redis()->set($key,$value);
    }



    /**
     * 设置Redis数据
     */
    private static function getRedis($key)
    {
        return redis()->get($key);
    }
}